package com.vhuomai.wechat.service.impl;

import com.quanyue.auto.util.AssertUtils;
import com.quanyue.auto.util.GlobalContext;
import com.vhuomai.system.entity.BaseSysLink;
import com.vhuomai.system.entity.BaseSysMerchant;
import com.vhuomai.system.service.MerchantService;
import com.vhuomai.system.service.SystemLinkService;
import com.vhuomai.wechat.constant.MenuType;
import com.vhuomai.wechat.dto.WxMenuButtonWrapper;
import com.vhuomai.wechat.entity.BaseWxMenu;
import com.vhuomai.wechat.mapper.WxMenuMapper;
import com.vhuomai.wechat.service.WxMenuService;
import com.vhuomai.weixin.common.api.WxConsts;
import com.vhuomai.weixin.common.bean.menu.WxMenu;
import com.vhuomai.weixin.common.bean.menu.WxMenuButton;
import com.vhuomai.weixin.mp.api.WxMpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author harris
 */
@Service
public class WxMenuServiceImpl implements WxMenuService {
    
    @Autowired
    private WxMenuMapper menuMapper;
    @Autowired
    private WxMpService wxMpService;
    @Autowired
    private SystemLinkService linkService;
    @Autowired
    private MerchantService merchantService;
    
    private static final Integer ROOT = 0;
    private final Logger logger = LoggerFactory.getLogger(WxMenuServiceImpl.class);
    
    @Override
    public BaseWxMenu findById(Integer id) {
        return menuMapper.findOne(id);
    }
    
    @Override
    public List<BaseWxMenu> findAll() {
        return menuMapper.findByMerchantNo(GlobalContext.merchantNo());
    }
    
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public BaseWxMenu add(BaseWxMenu menu) {
        menu.setMerchantNo(GlobalContext.merchantNo());
        this.checkMenu(menu);
        menu.setCreateTime(LocalDateTime.now());
        menu.setUpdateTime(menu.getCreateTime());
        menu.setRelatedId("");
        menu.setMenuType(MenuType.TEXT);
        menuMapper.insert(menu);
        if (!menu.getParentId().equals(ROOT)) {
            BaseWxMenu parentMenu = menuMapper.findOne(menu.getParentId());
            parentMenu.setMenuType(MenuType.PARENT);
            parentMenu.setUpdateTime(LocalDateTime.now());
            menuMapper.update(parentMenu);
        }
        return menu;
    }
    
    @Override
    public void update(BaseWxMenu menu) {
        menu.setUpdateTime(LocalDateTime.now());
        menuMapper.updateSelective(menu);
    }
    
    @Override
    public void remove(Integer id) {
        menuMapper.deleteOne(id);
    }
    
    @Override
    public void syncToWx() {
        List<BaseWxMenu> menus = this.findAll();
        // get messages
        List<Integer> linkIds = menus.stream()
            .filter(menu -> menu.getMenuType() == MenuType.SYSTEM_LINK && menu.getRelatedId() != null)
            .map(menu -> Integer.parseInt(menu.getRelatedId()))
            .collect(Collectors.toList());
        Map<String, BaseSysLink> messages = this.getSystemLinks(linkIds);
        // get domain
        BaseSysMerchant merchant = merchantService.find();
        AssertUtils.notBlank(merchant.getDomain(), "请配置域名");
        // calculate menus
        Map<Integer, List<WxMenuButtonWrapper>> buttonGroups = menus
            .stream()
            .map(menu -> createButton(menu, merchant.getDomain(), messages.get(menu.getRelatedId())))
            .collect(Collectors.groupingBy(WxMenuButtonWrapper::getParent));
        WxMenu wxMenu = new WxMenu();
        wxMenu.setButtons(buttonGroups.get(ROOT)
            .stream()
            .map(wrapper -> {
                wrapper.getButton().setSubButtons(realButtons(buttonGroups.getOrDefault(wrapper.getId(), Collections.emptyList())));
                return wrapper.getButton();
            }).collect(Collectors.toList()));
        try {
            String result = wxMpService.getMenuService().menuCreate(wxMenu);
            logger.info(result);
        } catch (Exception e) {
            throw new RuntimeException("保存菜单出错", e);
        }
    }
    
    private void checkMenu(BaseWxMenu menu) {
        int count = menuMapper.findCount(menu.getMerchantNo(), menu.getParentId());
        int limit = menu.getParentId().equals(ROOT) ? 3 : 5;
        AssertUtils.isTrue(count < limit, "超出微信菜单限制");
    }
    
    private Map<String, BaseSysLink> getSystemLinks(List<Integer> messageIds) {
        if (!messageIds.isEmpty()) {
            return linkService.findByIds(messageIds)
                .stream()
                .collect(Collectors.toMap(menu -> String.valueOf(menu.getId()), s -> s));
        }
        return Collections.emptyMap();
    }
    
    private List<WxMenuButton> realButtons(List<WxMenuButtonWrapper> wrappers) {
        return wrappers.stream()
            .map(WxMenuButtonWrapper::getButton)
            .collect(Collectors.toList());
    }
    
    private WxMenuButtonWrapper createButton(BaseWxMenu menu, String domain, BaseSysLink systemLink) {
        WxMenuButton wxMenuButton = new WxMenuButton();
        wxMenuButton.setName(menu.getName());
        switch (menu.getMenuType()) {
            case LINK:
            case NEWS_LINK:
                wxMenuButton.setType(WxConsts.BUTTON_VIEW);
                wxMenuButton.setUrl(menu.getContent());
                break;
            case SYSTEM_LINK:
                wxMenuButton.setType(WxConsts.BUTTON_VIEW);
                String url = domain + systemLink.getUrl();
                wxMenuButton.setUrl(url);
                break;
            case NEWS:
            case IMAGE:
            case VOICE:
            case VIDEO:
                wxMenuButton.setType(WxConsts.BUTTON_MEDIA_ID);
                wxMenuButton.setMediaId(menu.getRelatedId());
                break;
            case TEXT:
                wxMenuButton.setType(WxConsts.BUTTON_CLICK);
                wxMenuButton.setKey(String.valueOf(menu.getId()));
                break;
            case PARENT:
                break;
            default:
                throw new RuntimeException("unsupported type " + menu.getMenuType());
        }
        WxMenuButtonWrapper wrapper = new WxMenuButtonWrapper();
        wrapper.setButton(wxMenuButton);
        wrapper.setId(menu.getId());
        wrapper.setParent(menu.getParentId());
        return wrapper;
    }
}
